{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EheA5_j_cEwc"
      },
      "source": [
        "##### Copyright 2019 Google LLC.\n",
        "\n",
        "Licensed under the Apache License, Version 2.0 (the \"License\");"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YCriMWd-pRTP"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\"); { display-mode: \"form\" }\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OvRwFTkqcp1e"
      },
      "source": [
        "# Introduction to TensorFlow Part 2 - Debugging and Control Flow\n",
        "\n",
        "\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n",
        "  \u003ctd\u003e\n",
        "    \u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/google/tf-quant-finance/blob/master/tf_quant_finance/examples/jupyter_notebooks/Introduction_to_TensorFlow_Part_2_-_Debugging_and_Control_Flow.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" /\u003eRun in Google Colab\u003c/a\u003e\n",
        "  \u003c/td\u003e\n",
        "  \u003ctd\u003e\n",
        "    \u003ca target=\"_blank\" href=\"https://github.com/google/tf-quant-finance/blob/master/tf_quant_finance/examples/jupyter_notebooks/Introduction_to_TensorFlow_Part_2_-_Debugging_and_Control_Flow.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" /\u003eView source on GitHub\u003c/a\u003e\n",
        "  \u003c/td\u003e\n",
        "\u003c/table\u003e"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "Oi_zYoOWjR88"
      },
      "outputs": [],
      "source": [
        "#@title Upgrade to TensorFlow 2.1+\r\n",
        "!pip install --upgrade tensorflow"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "metadata": {
        "executionInfo": {
          "elapsed": 527,
          "status": "ok",
          "timestamp": 1614182005905,
          "user": {
            "displayName": "",
            "photoUrl": "",
            "userId": ""
          },
          "user_tz": 0
        },
        "id": "nPX9m7Q_w_8p"
      },
      "outputs": [],
      "source": [
        "#@title Install and import Libraries for this colab. RUN ME FIRST!\n",
        "!pip install matplotlib\n",
        "import matplotlib.pyplot as plt\n",
        "import tensorflow as tf\n",
        "from tensorflow.python.summary.writer.writer import FileWriter\n",
        "%load_ext tensorboard\n",
        "\n",
        "# Use graph mode for this training\n",
        "#tf.compat.v1.disable_eager_execution()\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "obZHjPQScSqd"
      },
      "source": [
        "# What this notebook covers\n",
        "\n",
        "This notebook carries on from [part 1](https://colab.research.google.com/github/google/tf-quant-finance/blob/master/tf_quant_finance/examples/jupyter_notebooks/Introduction_to_TensorFlow_Part_1_-_Basics.ipynb), and covers the basics of control flow and debugging in tensorflow: \n",
        "*   various debugging aids\n",
        "*   loops\n",
        "*   conditionals\n",
        "\n",
        "These features are used in graph mode of inside a `tf.function`. For simplicity eager execution is disabled throughout the training.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pVLqCYzuDhfT"
      },
      "source": [
        "# Debugging"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_md9nF-EbCcn"
      },
      "source": [
        "## tf.py_function\n",
        "\n",
        "[Full documentation](https://www.tensorflow.org/api_docs/python/tf/py_function)\n",
        "\n",
        "This allows you to wrap a python function as an op. The function can make further calls into tensorflow, allowing e.g. a subset of tensorflow operations to be wrapped up inside the function and inspected using pdb. \n",
        "\n",
        "There are various restrictions involved with this op and \n",
        "*   serializing execution graphs\n",
        "*   executing across distributed machines\n",
        "\n",
        "Read the documentation for more information. As such, this should be viewed as more of a debugging tool, and its use should be avoided in performance-sensitive code.\n",
        "\n",
        "\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ff0Ex7zkfw6o"
      },
      "outputs": [],
      "source": [
        "def plus_one(x):\n",
        "  print(\"input has type %s, value %s\"%(type(x), x))\n",
        "  output = x + 1.0\n",
        "  print(\"output has type %s, value %s\"%(type(output), output))\n",
        "  return output\n",
        "\n",
        "x = tf.constant([1.0,2.0,3.0])\n",
        "\n",
        "a = tf.py_function(plus_one, inp = [x], Tout=tf.float32)\n",
        "\n",
        "with tf.compat.v1.Session() as sess:\n",
        "  print(sess.run(a))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NHt4uXO6AadV"
      },
      "source": [
        "## tf.print\n",
        "\n",
        "[Full Documentation](https://www.tensorflow.org/api_docs/python/tf/print)\n",
        "\n",
        "The `tf.print` op is another useful debugging tool. It takes any number of tensors and python objects, and prints them to stdout. There are a few optional parameters, to control formatting and where it prints to. See the documentation for details."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "cGlMeyLFDq1F"
      },
      "outputs": [],
      "source": [
        "a = tf.constant(1)\n",
        "print_trace = tf.print(\n",
        "    \"a has value\", a, \", type\", type(a), \"and shape\", tf.shape(a))\n",
        "\n",
        "with tf.compat.v1.Session() as sess:\n",
        "  results = sess.run(print_trace)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ou9Eph35D-sH"
      },
      "source": [
        "If you're using eager execution mode, that's all you need to know. For deferred execution however there are some significant complications that we'll discuss in the next section.\n",
        "\n",
        "Note that there's a similar op [tf.Print](https://www.tensorflow.org/api_docs/python/tf/Print) (note the capital P), that doesn't suffer from these complications, but the op is deprecated and due to disappear."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pt5xeqm7E8Vq"
      },
      "source": [
        "## tf.control_dependencies\n",
        "[Full Documentation](https://www.tensorflow.org/api_docs/python/tf/control_dependencies)\n",
        "\n",
        "One of the easiest optimisations tensorflow makes when in deferred mode is to eliminate unused ops. So if we run this:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "metadata": {
        "executionInfo": {
          "elapsed": 199,
          "status": "ok",
          "timestamp": 1614179941759,
          "user": {
            "displayName": "",
            "photoUrl": "",
            "userId": ""
          },
          "user_tz": 0
        },
        "id": "8nYVTSB4FMxg"
      },
      "outputs": [],
      "source": [
        "a = tf.constant(1) + tf.constant(1)\n",
        "print_trace = tf.print(\"a is set to \", a)\n",
        "b = a*2\n",
        "\n",
        "with tf.compat.v1.Session() as sess:\n",
        "  results = sess.run(b)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nkCbBSvLFeXp"
      },
      "source": [
        "Then we don't get any output. Nothing depends on ```print_trace``` (in fact nothing *can* depend on it: tf.print doesn't return anything to depend on), so it gets dropped from the graph before execution occurs. If you want ```print_trace``` to be evaluated, then you need to ask for it explicitly:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {
        "executionInfo": {
          "elapsed": 555,
          "status": "ok",
          "timestamp": 1614179968179,
          "user": {
            "displayName": "",
            "photoUrl": "",
            "userId": ""
          },
          "user_tz": 0
        },
        "id": "nbPBQ8QKF88O",
        "outputId": "a9b332bd-26c2-4835-960f-1a674fce468f"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "a is set to 2\n"
          ]
        }
      ],
      "source": [
        "a = tf.constant(1) + tf.constant(1)\n",
        "print_trace = tf.compat.v1.print(\"a is set to\", a)\n",
        "b = a*2\n",
        "\n",
        "with tf.compat.v1.Session() as sess:\n",
        "  results = sess.run((b,print_trace))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6QxaLTRaGfBt"
      },
      "source": [
        "That's fine for our noddy sample above. But obviously has problems as your graph grows larger or the `sess.run` method gets further removed from the graph definition. The solution for that is ```tf.control_dependencies```. This signals to tensorflow that the given set of prerequisite ops must be evaluated before a set of dependent ops. \n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {
        "executionInfo": {
          "elapsed": 196,
          "status": "ok",
          "timestamp": 1614180119402,
          "user": {
            "displayName": "",
            "photoUrl": "",
            "userId": ""
          },
          "user_tz": 0
        },
        "id": "2aNBMcvcIuaf",
        "outputId": "42628e21-ddfc-46b0-aac1-2ee7f604b20d"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "hello world\n",
            "a is set to 2\n"
          ]
        }
      ],
      "source": [
        "a = tf.constant(1) + tf.constant(1)\n",
        "print_trace = tf.print(\"a is set to\", a)\n",
        "hello_world = tf.print(\"hello world\")\n",
        "\n",
        "with tf.control_dependencies((print_trace, hello_world)):\n",
        "  # print_trace and hello_world will always be evaluated \n",
        "  # before b can be evaluated\n",
        "  b = a*2\n",
        "c = a*3\n",
        "\n",
        "with tf.compat.v1.Session() as sess:\n",
        "  results = sess.run(b)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lhuVDQ6QJT_X"
      },
      "source": [
        "Note that if all of the dependent ops are pruned from the dependency tree and thus not evaluated, then the prerequisites will not be evaluated either: e.g. if we call ```sess.run(c)``` in the example above,then `print_trace` and `hello_world` won't be evaluated\n",
        "\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "metadata": {
        "executionInfo": {
          "elapsed": 161,
          "status": "ok",
          "timestamp": 1614180170164,
          "user": {
            "displayName": "",
            "photoUrl": "",
            "userId": ""
          },
          "user_tz": 0
        },
        "id": "y76vDUESoq5B"
      },
      "outputs": [],
      "source": [
        "# Nothing gets printed\r\n",
        "with tf.compat.v1.Session() as sess:\r\n",
        "  results = sess.run(c)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oh_GyeVQZxtf"
      },
      "source": [
        "## tf.debugging.Assert\n",
        "[Full Documentation](https://www.tensorflow.org/api_docs/python/tf/debugging/Assert)\n",
        "\n",
        "In addition to ```tf.print```, the other common use of control_dependencies is ```tf.debugging.Assert```. This op does what you'd expect: checks a boolean condition and aborts execution with a InvalidArgumentError if the condition is not true. Just like ```tf.print```, it is likely to be pruned from the dependency tree and ignored if run in deferred execution mode without control_dependencies"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {
        "executionInfo": {
          "elapsed": 206,
          "status": "ok",
          "timestamp": 1614181119193,
          "user": {
            "displayName": "",
            "photoUrl": "",
            "userId": ""
          },
          "user_tz": 0
        },
        "id": "qSU7b6fCawNv",
        "outputId": "597089af-36d5-44bc-9b24-fdcad4397171"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Value of x is zero\n"
          ]
        }
      ],
      "source": [
        "x = tf.compat.v1.placeholder(tf.float32, shape=[])\n",
        "\n",
        "with tf.control_dependencies([\n",
        "    tf.debugging.Assert(tf.not_equal(x, 0), [\"Invalid value for x:\",x])]):\n",
        "  y = 2.0 / x\n",
        "\n",
        "\n",
        "with tf.compat.v1.Session() as sess:\n",
        "  try:\n",
        "    results = sess.run(y, feed_dict={x: 0.0})\n",
        "  except tf.errors.InvalidArgumentError:\n",
        "    print('Value of x is zero')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0sSUmD2HbkLt"
      },
      "source": [
        "There are also a bunch of helper methods, such as \n",
        "*   [assert_equal](https://www.tensorflow.org/api_docs/python/tf/debugging/assert_equal)\n",
        "*   [assert_positive](https://www.tensorflow.org/api_docs/python/tf/debugging/assert_positive)\n",
        "*   [assert_rank_at_least](https://www.tensorflow.org/api_docs/python/tf/debugging/assert_rank_at_least)\n",
        "*   etc.\n",
        "\n",
        "to simplify common uses of ```tf.Assert```\n",
        "\n",
        "So our sample above could have been written as"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {
        "executionInfo": {
          "elapsed": 154,
          "status": "ok",
          "timestamp": 1614181075383,
          "user": {
            "displayName": "",
            "photoUrl": "",
            "userId": ""
          },
          "user_tz": 0
        },
        "id": "fD9Ih3IacQmn",
        "outputId": "8e2ecd5f-e0b6-4618-d0cb-67832765470e"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Value of x is zero\n"
          ]
        }
      ],
      "source": [
        "x = tf.compat.v1.placeholder(tf.float32, shape=[])\n",
        "\n",
        "with tf.control_dependencies([tf.debugging.assert_none_equal(x, 0.0)]):\n",
        "  y = 2.0 / x\n",
        "\n",
        "with tf.compat.v1.Session() as sess:\n",
        "  try:\n",
        "    results = sess.run(y, feed_dict={x: 0.0})\n",
        "  except tf.errors.InvalidArgumentError:\n",
        "    print('Value of x is zero')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4hgH3jCrD6Sr"
      },
      "source": [
        "# Control Flow"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AVpDI6T9X7sZ"
      },
      "source": [
        "## tf.cond\n",
        "[Full Documentation](https://www.tensorflow.org/api_docs/python/tf/cond)\n",
        "\n",
        "The cond operand is the TensorFlow equivalent of if-else. It takes \n",
        "*   a condition, which must resolve down to a single scalar boolean\n",
        "*   true_fn: a python callable to generate one or more tensors that will be evaluated if the condition is true\n",
        "*   false_fn: same as true_fn, but the resulting tensors will only be evaluated if the the condition is false\n",
        "\n",
        "The condition is evaluated, and if its result is true then, the tensors generated by true_fn are evaluated and those generated by false_fn are abandoned (and vice versa if the result is false). \n",
        "\n",
        "Note that true_fn and false_fn must be python functions (or lambdas), not just tensors:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "metadata": {
        "executionInfo": {
          "elapsed": 569,
          "status": "ok",
          "timestamp": 1614181422188,
          "user": {
            "displayName": "",
            "photoUrl": "",
            "userId": ""
          },
          "user_tz": 0
        },
        "id": "2JfNrC8MFk5k",
        "outputId": "f63bf4ed-3450-4ec2-db91-c8fc93b74fcf"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "\u003ctf.Tensor 'cond_1/Identity:0' shape=() dtype=int32\u003e"
            ]
          },
          "execution_count": 6,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# This won't work\n",
        "try:\n",
        "  tf.cond(tf.constant(True), tf.constant(1), tf.constant(2))\n",
        "except TypeError:\n",
        "  pass\n",
        "# You need a callable:\n",
        "tf.cond(tf.constant(True), lambda: tf.constant(1), lambda: tf.constant(2))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zCKbDsTIGllL"
      },
      "source": [
        "The exact order of execution is a little complicated\n",
        "*   true_fn and false_fn are executed just once, when the graph is being built. \n",
        "*   the tensors created by true_fn (if the condition is true) or false_fn (if the condition is false) will be evaluated stricly after the condition has been evaluated\n",
        "*   the tensors created by false_fn (if the condition is true) or true_fn (if the condition is false) will never be evaluated\n",
        "*   any tensors depended on by the tensors generated by true_fn or false_fn will be always evaluated, even regardless of what the condition evaluates to"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "metadata": {
        "executionInfo": {
          "elapsed": 210,
          "status": "ok",
          "timestamp": 1614181470706,
          "user": {
            "displayName": "",
            "photoUrl": "",
            "userId": ""
          },
          "user_tz": 0
        },
        "id": "uhqRk_-pkPiF",
        "outputId": "48571d35-ba74-4aaf-8fe3-e2100e943e4f"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "****** Building graph\n",
            "TRUE_FN: I'm evaluated at graph building time\n",
            "FALSE_FN: I'm evaluated at graph building time\n",
            "****** Executing graph\n",
            "PREDICATE: I'm evaluated at execution time\n",
            "\n",
            "DEPENDENCY: I'm always evaluated at execution time because I'm a dependency\n",
            "\n",
            "TRUE_OP_FN: I'm evaluated at execution time because condition is True\n",
            "\n"
          ]
        }
      ],
      "source": [
        "def dependency_fn():\n",
        "  print (\"DEPENDENCY: I'm always evaluated at execution time because I'm a dependency\\n\")\n",
        "  return tf.constant(2)\n",
        "dependency = tf.py_function(dependency_fn, inp=[], Tout=tf.int32)\n",
        "\n",
        "def true_op_fn():\n",
        "  print (\"TRUE_OP_FN: I'm evaluated at execution time because condition is True\\n\")\n",
        "  return 1\n",
        "\n",
        "def true_fn():\n",
        "  print (\"TRUE_FN: I'm evaluated at graph building time\")\n",
        "  return tf.py_function(true_op_fn, inp=[], Tout=tf.int32)\n",
        "\n",
        "def false_op_fn(input):\n",
        "  print (\"FALSE_OP_FN: I'm never evaluated because condition isn't False\\n\")\n",
        "  return 1 + input\n",
        "\n",
        "def false_fn():\n",
        "  print (\"FALSE_FN: I'm evaluated at graph building time\")\n",
        "  return tf.py_function(false_op_fn, inp=[dependency], Tout=tf.int32)\n",
        "\n",
        "\n",
        "print(\"****** Building graph\")\n",
        "\n",
        "def predicate_fn():\n",
        "  print(\"PREDICATE: I'm evaluated at execution time\\n\")\n",
        "  return tf.constant(True)\n",
        "x = tf.cond(tf.py_function(predicate_fn, inp=[], Tout=tf.bool), true_fn, false_fn)\n",
        "\n",
        "print(\"****** Executing graph\")\n",
        "with tf.compat.v1.Session() as sess:\n",
        "  sess.run(x)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HXDMqSKFQozZ"
      },
      "source": [
        "## tf.while_loop\n",
        "[Full Documentation](https://www.tensorflow.org/api_docs/python/tf/while_loop)\n",
        "\n",
        "This is one of the more complicated ops in TensorFlow, so we'll take things step by step. \n",
        "\n",
        "*   The most important parameter is ```loop_vars```. This is a tuple/list of tensors.\n",
        "\n",
        "*   Next up is ```cond``` this is a python callable that should take the same number of arguments as loop_vars contains, and returns a single boolean.\n",
        "\n",
        "*   The third important parameter is ```body```. This is a python callable that should take the same number of arguments as loop_vars contains, and returns a tuple/list of values with the same size as loop_vars, and whose members are of the same type/arity/shape as those in loop_vars.\n",
        "\n",
        "Note that like the ```trun_fn``` and ```false_fn``` parameters discussed in tf.cond above, ```body``` and ```cond``` are callables, that are called once during graph definition.\n",
        "\n",
        "To a first level approximation, the behaviour is then roughtly akin to the following pseudo-code:\n",
        "```python\n",
        "working_vars = loop_vars\n",
        "while(cond(*working_vars)):\n",
        "  working_vars = body(*working_vars)\n",
        "return working_vars\n",
        "```\n",
        "\n",
        "\n",
        "There are optional complications:\n",
        "*   By default, each loop variable must have exactly the same shape/size/arity in all iterations. If you don't want that (e.g. because you want to increase the size in a particular dimnension by 1 each iteration), then you can use ```shape_invariants``` to loosen the checks.\n",
        "*   ```maximum_iterations``` can be used to put an upper bound on number of times the loop is executed, even if cond still returns true\n",
        "\n",
        "The documentation contains some warnings about parallel executions, race conditions and variables getting out of sync. To understand these, we need to beyond the first level approximation above. Consider the following:\n",
        "```python\n",
        "index = tf.constant(1)\n",
        "accumulator = tf.constant(0)\n",
        "\n",
        "loop = tf.while_loop(\n",
        "    loop_vars=[index, accumulator], \n",
        "    cond = lambda idx, acc: idx \u003c 4, \n",
        "    body = lambda idx, acc: [idx+1, acc + idx] )\n",
        "```\n",
        "\n",
        "To a second level approximation, this is equivalent to\n",
        "\n",
        "```python\n",
        "index_initial = tf.constant(1)\n",
        "accumalator_initial = tf.constant(0)\n",
        "\n",
        "index_iteration_1 = tf.add(index_initial, 1)\n",
        "index_iteration_2 = tf.add(index_iteration_1, 1)\n",
        "index_iteration_3 = tf.add(index_iteration_2, 1)\n",
        "\n",
        "accumulator_iteration_1 = tf.add(\n",
        "  accumalator_initial, index_initial)\n",
        "accumulator_iteration_2 = tf.add(\n",
        "  accumulator_iteration_1, index_iteration_1)\n",
        "accumulator_iteration_3 = tf.add(\n",
        "  accumulator_iteration_2, index_iteration_2)\n",
        "\n",
        "loop = [index_iteration_3, accumulator_iteration_3]\n",
        "```\n",
        "(for the full, unapproximated, gory details of the graph, run the code below"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8iBvXDmZ0C55"
      },
      "outputs": [],
      "source": [
        "tf.compat.v1.reset_default_graph()\n",
        "\n",
        "index = tf.constant(1)\n",
        "accumulator = tf.constant(0)\n",
        "\n",
        "loop = tf.while_loop(\n",
        "    loop_vars=[index, accumulator], \n",
        "    cond = lambda idx, acc: idx \u003c 4, \n",
        "    body = lambda idx, acc: [idx+1, acc + idx] )\n",
        "\n",
        "with tf.compat.v1.Session() as sess:\n",
        "  with FileWriter(\"logs\", sess.graph):\n",
        "    results = sess.run(loop)\n",
        "  \n",
        "%tensorboard --logdir logs"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_t7YDFWe4T3F"
      },
      "source": [
        "but our second-level approximation will do for now).\n",
        "\n",
        "Notice how index_iteration_3 doesn't depend the accumulator values at all. Thus assuming our accumulator tensors were doing something more complicated than adding two integers, and assuming we were running on hardware with plenty of execution units, then it's possible that index_iteration_3 could be fully calculated in one thead, while accumulator_iteration_1 is still being calculated in another.\n",
        "\n",
        "It usually doesn't matter, because loop depends on both index_iteration_3 and accuulator_iteration_3, so it and any dependencies can't start their evaluation before all the accumulation steps have completed. But if you're depending on side effects, or clever operations depending on global state that tensorflow is unware of (e.g. in custom kernels, or py_function ops), then it's something to be aware of. You can use the while_loop's parallel_iterations parameter to restrict the number of iterations that can be calulated in parallel if this does become an issue."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QZvhWzjGhVEi"
      },
      "source": [
        "## Exercise: Mandlebrot set\n",
        "\n",
        "Recall that the Mandelbrot set is defined as the set of values of $c$ in the complex plane such that the recursion:  \n",
        "$z_{n+1} = z_{n}^2 + c$ does not diverge.\n",
        "\n",
        "It is known that all such values of $c$ lie inside the circle of radius $2$ around the origin.\n",
        "\n",
        "So what we'll do is\n",
        "\n",
        "\n",
        "*   Create a 2-d tensor ```c_values``` ranging from -1-1i to 1+1i\n",
        "*   Create a matching 2-d tensor ```Z_values``` with initial values of 0\n",
        "*   Create a third 2-d tensor ```diverged_after``` which contains the iteration number that the matching Z_value's absolute value was \u003e 2 (or MAX_ITERATIONS, if it always stayed below 2)\n",
        "*   Update the above using a while_loop\n",
        "*   display the final values of ```diverged_after``` as an image to see the famous shape\n",
        "\n",
        "\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "metadata": {
        "executionInfo": {
          "elapsed": 248,
          "status": "ok",
          "timestamp": 1614182210272,
          "user": {
            "displayName": "",
            "photoUrl": "",
            "userId": ""
          },
          "user_tz": 0
        },
        "id": "DZZxF2l0it9-"
      },
      "outputs": [],
      "source": [
        "MAX_ITERATIONS = 64\n",
        "NUM_PIXELS = 512\n",
        "\n",
        "def GenerateGrid(nX, nY, bottom_left=(-1.0, -1.0), top_right=(1.0, 1.0)):\n",
        "  \"\"\"Generates a complex matrix of shape [nX, nY].\n",
        "  \n",
        "  Generates an evenly spaced grid of complex numbers spanning the rectangle \n",
        "  between the supplied diagonal points. \n",
        "  \n",
        "  Args:\n",
        "    nX: A positive integer. The number of points in the horizontal direction.\n",
        "    nY: A positive integer. The number of points in the vertical direction.\n",
        "    bottom_left: The coordinates of the bottom left corner of the rectangle to\n",
        "      cover.\n",
        "    top_right: The coordinates of the top right corner of the rectangle to\n",
        "      cover.\n",
        "\n",
        "  Returns:\n",
        "    A constant tensor of type complex64 and shape [nX, nY].\n",
        "  \"\"\"\n",
        "  x = tf.linspace(bottom_left[0], top_right[0], nX)\n",
        "  y = tf.linspace(bottom_left[1], top_right[1], nY)\n",
        "  real, imag = tf.meshgrid(x, y)\n",
        "  return tf.cast(tf.complex(real, imag), tf.complex128)\n",
        "\n",
        "c_values = GenerateGrid(NUM_PIXELS, NUM_PIXELS)\n",
        "initial_Z_values = tf.zeros_like(c_values, dtype=tf.complex128)\n",
        "initial_diverged_after = tf.ones_like(c_values, dtype=tf.int32) * MAX_ITERATIONS\n",
        "\n",
        "# You need to put the various values you want to change inside the loop here\n",
        "loop_vars = ()\n",
        "\n",
        "# this needs to take the same number of arguments as loop_vars contains and\n",
        "# return a tuple of equal size with the next iteration's values\n",
        "def body():\n",
        "  # hint: tf.abs will give the magnitude of a complex value\n",
        "  return ()\n",
        "\n",
        "# this just needs to take the same number of arguments as loop_vars contains and\n",
        "# return true (we'll use maximum_iterations to exit the loop)\n",
        "def cond():\n",
        "  return True\n",
        "\n",
        "loop = tf.while_loop(\n",
        "    loop_vars=loop_vars, \n",
        "    body = body, \n",
        "    cond = cond, \n",
        "    maximum_iterations=MAX_ITERATIONS)\n",
        "\n",
        "with tf.compat.v1.Session() as sess:\n",
        "  results = sess.run(loop)\n",
        "\n",
        "## extract the final value of diverged_after from the tuple\n",
        "final_diverged_after = results[-1]\n",
        "plt.matshow(final_diverged_after)\n",
        "pass\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "metadata": {
        "colab": {
          "height": 275
        },
        "executionInfo": {
          "elapsed": 1068,
          "status": "ok",
          "timestamp": 1614183652561,
          "user": {
            "displayName": "",
            "photoUrl": "",
            "userId": ""
          },
          "user_tz": 0
        },
        "id": "JSitXn3PyIIe",
        "outputId": "5a4aff0f-395d-4ac6-feb3-5a6617b10bd7"
      },
      "outputs": [
        {
          "data": {
            "image/png": "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\nhLjm/75cyfREULhpAAAAAElFTkSuQmCC\n",
            "text/plain": [
              "\u003cFigure size 400x400 with 1 Axes\u003e"
            ]
          },
          "metadata": {
            "tags": []
          },
          "output_type": "display_data"
        }
      ],
      "source": [
        "#@title Solution: Mandlebrot set (Double-click to reveal)\n",
        "\n",
        "MAX_ITERATIONS = 64\n",
        "NUM_PIXELS = 512\n",
        "\n",
        "def GenerateGrid(nX, nY, bottom_left=(-1.0, -1.0), top_right=(1.0, 1.0)):\n",
        "  \"\"\"Generates a complex matrix of shape [nX, nY].\n",
        "  \n",
        "  Generates an evenly spaced grid of complex numbers spanning the rectangle \n",
        "  between the supplied diagonal points. \n",
        "  \n",
        "  Args:\n",
        "    nX: A positive integer. The number of points in the horizontal direction.\n",
        "    nY: A positive integer. The number of points in the vertical direction.\n",
        "    bottom_left: The coordinates of the bottom left corner of the rectangle to\n",
        "      cover.\n",
        "    top_right: The coordinates of the top right corner of the rectangle to\n",
        "      cover.\n",
        "\n",
        "  Returns:\n",
        "    A constant tensor of type complex64 and shape [nX, nY].\n",
        "  \"\"\"\n",
        "  x = tf.linspace(bottom_left[0], top_right[0], nX)\n",
        "  y = tf.linspace(bottom_left[1], top_right[1], nY)\n",
        "  real, imag = tf.meshgrid(x, y)\n",
        "  return tf.cast(tf.complex(real, imag), tf.complex128)\n",
        "\n",
        "c_values = GenerateGrid(NUM_PIXELS, NUM_PIXELS)\n",
        "initial_Z_values = tf.zeros_like(c_values, dtype=tf.complex128)\n",
        "initial_diverged_after = tf.ones_like(c_values, dtype=tf.int32) * MAX_ITERATIONS\n",
        "\n",
        "# You need to put the various values you want to change inside the loop here\n",
        "loop_vars = (0, initial_Z_values, initial_diverged_after)\n",
        "\n",
        "# this needs to take the same number of arguments as loop_vars contains and\n",
        "# return a tuple of equal size with the next iteration's values\n",
        "def body(iteration_count, Z_values, diverged_after):\n",
        "  new_Z_values = Z_values * Z_values + c_values\n",
        "  has_diverged = tf.abs(new_Z_values) \u003e 2.0\n",
        "  new_diverged_after = tf.minimum(diverged_after, tf.where(\n",
        "      has_diverged, iteration_count, MAX_ITERATIONS))\n",
        "  return (iteration_count+1, new_Z_values, new_diverged_after)\n",
        "\n",
        "# this just needs to take the same number of arguments as loop_vars contains and\n",
        "# return true (we'll use maximum_iterations to exit the loop)\n",
        "def cond(iteration_count, Z_values, diverged_after):\n",
        "  return True\n",
        "\n",
        "loop = tf.while_loop(\n",
        "    loop_vars=loop_vars, \n",
        "    body = body, \n",
        "    cond = cond, \n",
        "    maximum_iterations=MAX_ITERATIONS)\n",
        "\n",
        "with tf.compat.v1.Session() as sess:\n",
        "  results = sess.run(loop)\n",
        "\n",
        "## extract the final value of diverged_after from the tuple\n",
        "final_diverged_after = results[-1]\n",
        "plt.matshow(final_diverged_after)\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mPkGExX8wioI"
      },
      "outputs": [],
      "source": [
        ""
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "last_runtime": {
        "build_target": "",
        "kind": "local"
      },
      "name": "Introduction to TensorFlow Part 2 - Debugging and Control Flow.ipynb",
      "provenance": [],
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
